package com.ease.actionscript.mapping.core;

import java.io.File;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.ease.actionscript.mapping.mapper.AbstractActionScriptTypeMapper;
import com.ease.actionscript.mapping.mapper.AbstractValueObjectClassMapper;
import com.ease.actionscript.mapping.mapper.IncludeRemotingClassesClassMapper;
import com.ease.actionscript.mapping.mapper.RemotingDestinationContextMapper;
import com.ease.actionscript.mapping.mapper.ServiceManagerClassMapper;
import com.ease.actionscript.mapping.mapper.ServiceOperationClassMapper;
import com.ease.actionscript.mapping.mapper.ValueObjectClassMapper;
import com.ease.actionscript.mapping.mapper.ValueObjectEnumMapper;
import com.ease.actionscript.mapping.mapper.ValueObjectInterfaceMapper;
import com.ease.actionscript.mapping.vo.ActionScriptMappingContext;
import com.ease.common.util.Detect;
import com.ease.common.util.FileUtil;
import com.ease.common.util.OutputUtil;
import com.ease.common.vo.ClasspathEntry;
import com.ease.common.vo.ProjectClasspath;
import com.ease.oxm.jaxb.JaxbUtil;

/**
 * 
 * @author nathanleewei
 * 
 */
public class ActionScriptMappingProject extends Detect implements ActionScriptMappingContext {

	private static final String[] EXTENSIONS = new String[] { "java" };

	private static final String[] SUFFIXS_VALUEOBJECT = new String[] { ".enums", ".message", ".datastructure", ".model", ".criteria", ".criterion", ".vo", ".type" };

	private static final String[] SUFFIXS_SERVICEOPERATION = new String[] { ".service", ".management" };

	private String javaWorkspacePath;

	private String flexWorkspacePath;

	private String[] projectNames;

	private String[] packagePrefixs;

	private String applicationProject;

	// private String destinationSourceFolder;// source folder

	private Map<String, Set<String>> remotingDestinationNamesMap = new HashMap<String, Set<String>>();

	// private Set<String> remotingDestinationNames = new HashSet<String>();

	private Map<String, Set<Class<?>>> remotingDestinationClassesMap = new HashMap<String, Set<Class<?>>>();

	// private Set<Class<?>> remotingDestinationClasses = new HashSet<Class<?>>();

	// private Set<Class<?>> includeClasses = new HashSet<Class<?>>();

	private Map<String, Set<Class<?>>> includeClassesMap = new HashMap<String, Set<Class<?>>>();

	// private String applicationContextRemotingDestination;

	private String[] applicationContextConfigLocations;

	// private String applicationContextFileName;

	private String[] voIncludePackages;

	private ApplicationContext applicationContext;

	private Set<String> requireProjectBundle = new HashSet<String>();

	public void addIncludeClass(Class<?> includeClass, String projectName) {
		projectName = AbstractActionScriptTypeMapper.buildDestinationProjectName(projectName);
		Set<Class<?>> set = includeClassesMap.get(projectName);
		if (null == set) {
			set = new HashSet<Class<?>>();
			includeClassesMap.put(projectName, set);
		}
		set.add(includeClass);
	}

	public Set<Class<?>> getIncludeClasses(String projectName) {
		projectName = AbstractActionScriptTypeMapper.buildDestinationProjectName(projectName);
		return includeClassesMap.get(projectName);
	}

	public boolean containRemotingDestinationClass(Class<?> remotingDestinationClass, String projectName) {
		projectName = AbstractActionScriptTypeMapper.buildDestinationProjectName(projectName);
		Set<Class<?>> set = remotingDestinationClassesMap.get(projectName);
		if (null != set && set.contains(remotingDestinationClass.getName())) {
			return true;
		}
		// return remotingDestinationNames.contains(remotingDestinationClass.getName());
		return false;
	}

	public void addRemotingDestinationClass(Class<?> remotingDestinationClass, String projectName) {
		projectName = AbstractActionScriptTypeMapper.buildDestinationProjectName(projectName);

		Set<Class<?>> classSet = remotingDestinationClassesMap.get(projectName);
		Set<String> nameSet = remotingDestinationNamesMap.get(projectName);
		if (null == classSet) {
			classSet = new HashSet<Class<?>>();
			remotingDestinationClassesMap.put(projectName, classSet);
			nameSet = new HashSet<String>();
			remotingDestinationNamesMap.put(projectName, nameSet);
		}
		nameSet.add(remotingDestinationClass.getName());
		classSet.add(remotingDestinationClass);

		// remotingDestinationNames.add(remotingDestinationClass.getName());
		// remotingDestinationClasses.add(remotingDestinationClass);

	}

	public Set<Class<?>> getRemotingDestinationClasses(String projectName) {
		projectName = AbstractActionScriptTypeMapper.buildDestinationProjectName(projectName);
		return remotingDestinationClassesMap.get(projectName);
	}

	public void execute() {

		applicationContext = new ClassPathXmlApplicationContext(applicationContextConfigLocations);

		if (null != projectNames) {
			for (String projectName : projectNames) {
				this.recuisiveMapper(javaWorkspacePath.trim(), projectName.trim(), true);
			}
		}

		if (null != requireProjectBundle) {
			for (String projectName : requireProjectBundle) {
				ServiceManagerClassMapper serviceManagerClassMapper = new ServiceManagerClassMapper();
				serviceManagerClassMapper.setActionScriptMappingContext(this);
				serviceManagerClassMapper.setProjectName(projectName);
				serviceManagerClassMapper.generate();

				RemotingDestinationContextMapper remotingDestinationContextMapper = new RemotingDestinationContextMapper();
				remotingDestinationContextMapper.setActionScriptMappingContext(this);
				remotingDestinationContextMapper.setProjectName(projectName);
				remotingDestinationContextMapper.generate();

				IncludeRemotingClassesClassMapper includeClassesClassMapper = new IncludeRemotingClassesClassMapper();
				includeClassesClassMapper.setActionScriptMappingContext(this);
				includeClassesClassMapper.setProjectName(projectName);
				includeClassesClassMapper.generate();
			}
		}

		OutputUtil.p("done");

	}

	@SuppressWarnings("unchecked")
	public void recuisiveMapper(String workspacePath, String projectName, boolean recuisive) {
		requireProjectBundle.add(projectName);
		if (recuisive) {

			String classpathPath = workspacePath + "/" + projectName + "/.classpath";
			String string = FileUtil.readFileSystemResourceToString(classpathPath);

			ProjectClasspath projectClasspath = JaxbUtil.xmlToObject(string, ProjectClasspath.class);
			if (null == projectClasspath) {
				return;
			}

			for (ClasspathEntry classpathEntry : projectClasspath.getClasspathEntries()) {
				if (null != classpathEntry.getCombineAccessRules()) {
					String path = classpathEntry.getPath();
					String name = path.substring(1);
					if (!requireProjectBundle.contains(name)) {
						recuisiveMapper(workspacePath, name.trim(), recuisive);
					}
				}
			}

		}

		/** generate actionscript codes */
		String srcPath = AbstractActionScriptTypeMapper.absoluteSourcePath(workspacePath, projectName);

		Iterator<File> iterator = (Iterator<File>) FileUtils.iterateFiles(new File(srcPath), EXTENSIONS, true);

		String path = null;
		while (iterator.hasNext()) {
			File file = iterator.next();
			if (!file.isDirectory()) {
				path = file.getAbsolutePath();
				path = path.substring(srcPath.length() + 1);
				path = StringUtils.replace(path, "/", ".");
				path = StringUtils.replace(path, "\\", ".");

				String className = path.substring(0, path.length() - 5);

				try {

					boolean compliant = false;

					Class<?> clazz = Class.forName(className);
					String packageName = clazz.getPackage().getName();

					if (endsWith(packageName, SUFFIXS_SERVICEOPERATION)) {

						ServiceOperationClassMapper serviceOperationClassMapper = new ServiceOperationClassMapper();
						serviceOperationClassMapper.setActionScriptMappingContext(this);
						serviceOperationClassMapper.setClazz(clazz);
						serviceOperationClassMapper.setProjectName(projectName);
						serviceOperationClassMapper.setApplicationContext(applicationContext);
						compliant = compliant || serviceOperationClassMapper.generate();

					} else if (endsWith(packageName, SUFFIXS_VALUEOBJECT) || contains(packageName, this.getVoIncludePackages())) {
						AbstractValueObjectClassMapper abstractValueObjectTypeMapper = null;
						if (clazz.isEnum()) {
							ValueObjectEnumMapper valueObjectEnumMapper = new ValueObjectEnumMapper();
							valueObjectEnumMapper.setActionScriptMappingContext(this);
							valueObjectEnumMapper.setProjectName(projectName);
							valueObjectEnumMapper.setClazz(clazz);
							abstractValueObjectTypeMapper = valueObjectEnumMapper;
						} else if (Modifier.isInterface(clazz.getModifiers())) {
							ValueObjectInterfaceMapper valueObjectInterfaceMapper = new ValueObjectInterfaceMapper();
							valueObjectInterfaceMapper.setActionScriptMappingContext(this);
							valueObjectInterfaceMapper.setProjectName(projectName);
							valueObjectInterfaceMapper.setClazz(clazz);
							abstractValueObjectTypeMapper = valueObjectInterfaceMapper;
						} else {
							ValueObjectClassMapper valueObjectClassMapper = new ValueObjectClassMapper();
							valueObjectClassMapper.setActionScriptMappingContext(this);
							valueObjectClassMapper.setProjectName(projectName);
							valueObjectClassMapper.setClazz(clazz);
							abstractValueObjectTypeMapper = valueObjectClassMapper;
						}

						compliant = compliant || abstractValueObjectTypeMapper.generate();

					}

					OutputUtil.p("[" + compliant + "] " + className);

				} catch (ClassNotFoundException e) {
					e.printStackTrace();
				}
			}

		}
	}

	// /**
	// * @return the applicationContextRemotingDestination
	// */
	// public String getApplicationContextDestination() {
	// return applicationContextRemotingDestination;
	// }
	//
	// /**
	// * @param applicationContextRemotingDestination
	// * the applicationContextRemotingDestination to set
	// */
	// public void setApplicationContextDestination(String applicationContextDestination) {
	// this.applicationContextRemotingDestination = applicationContextDestination;
	// }

	/**
	 * @return the voIncludePackages
	 */
	public String[] getVoIncludePackages() {
		return voIncludePackages;
	}

	/**
	 * @param voIncludePackages
	 *            the voIncludePackages to set
	 */
	public void setVoIncludePackages(String[] voIncludePackages) {
		this.voIncludePackages = voIncludePackages;
	}

	/**
	 * @return the javaProjectNames
	 */
	public String[] getProjectNames() {
		return projectNames;
	}

	/**
	 * @param javaProjectNames
	 *            the javaProjectNames to set
	 */
	public void setProjectNames(String[] projectNames) {
		this.projectNames = projectNames;
	}

	// /**
	// * @return the applicationContextFileName
	// */
	// public String getApplicationContextFileName() {
	// return applicationContextFileName;
	// }
	//
	// /**
	// * @param applicationContextFileName
	// * the applicationContextFileName to set
	// */
	// public void setApplicationContextFileName(String applicationContextFileName) {
	// this.applicationContextFileName = applicationContextFileName;
	// }

	/**
	 * @return the applicationContextConfigLocations
	 */
	public String[] getApplicationContextConfigLocations() {
		return applicationContextConfigLocations;
	}

	/**
	 * @param applicationContextConfigLocations
	 *            the applicationContextConfigLocations to set
	 */
	public void setApplicationContextConfigLocations(String[] applicationContextConfigLocations) {
		this.applicationContextConfigLocations = applicationContextConfigLocations;
	}

	/**
	 * @return the applicationContext
	 */
	public ApplicationContext getApplicationContext() {
		return applicationContext;
	}

	/**
	 * @param applicationContext
	 *            the applicationContext to set
	 */
	public void setApplicationContext(ApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
	}

	//
	// /**
	// * @return the destinationSourceFolder
	// */
	// public String getDestinationSourceFolder() {
	// return destinationSourceFolder;
	// }
	//
	// /**
	// * @param destinationSourceFolder
	// * the destinationSourceFolder to set
	// */
	// public void setDestinationSourceFolder(String destinationSourceFolder) {
	// this.destinationSourceFolder = destinationSourceFolder;
	// }

	/**
	 * @return the flexWorkspacePath
	 */
	public String getFlexWorkspacePath() {
		return flexWorkspacePath;
	}

	/**
	 * @param flexWorkspacePath
	 *            the flexWorkspacePath to set
	 */
	public void setFlexWorkspacePath(String flexWorkspacePath) {
		this.flexWorkspacePath = flexWorkspacePath;
	}

	/**
	 * @return the javaWorkspacePath
	 */
	public String getJavaWorkspacePath() {
		return javaWorkspacePath;
	}

	/**
	 * @param javaWorkspacePath
	 *            the javaWorkspacePath to set
	 */
	public void setJavaWorkspacePath(String javaWorkspacePath) {
		this.javaWorkspacePath = javaWorkspacePath;
	}

	/**
	 * @return the packagePrefixs
	 */
	public String[] getPackagePrefixs() {
		return packagePrefixs;
	}

	/**
	 * @param packagePrefixs
	 *            the packagePrefixs to set
	 */
	public void setPackagePrefixs(String[] packagePrefixs) {
		this.packagePrefixs = packagePrefixs;
	}

	/**
	 * @return the requireProjectBundle
	 */
	public Set<String> getRequireProjectBundle() {
		return requireProjectBundle;
	}

	/**
	 * @param requireProjectBundle
	 *            the requireProjectBundle to set
	 */
	public void setRequireProjectBundle(Set<String> requireProjectBundle) {
		this.requireProjectBundle = requireProjectBundle;
	}

	/**
	 * @return the applicationProject
	 */
	public String getApplicationProject() {
		return applicationProject;
	}

	/**
	 * @param applicationProject
	 *            the applicationProject to set
	 */
	public void setApplicationProject(String applicationProject) {
		this.applicationProject = applicationProject;
	}
}
